#include "server.h"
#include "constants.h"

extern int debug_flag;

/**
 * Resolves write request.
 * @param port port from which client sent request
 * @param filename filename of the file for writing
 * @param client_addr address of the client
 * @param mode mode of the transfer
 */
void resolve_write_rq(in_port_t port, char *filename, char *client_addr, mode_type mode) {
	char buffer[6];
	snprintf(buffer, 6, "%d", port);
		
	struct addrinfo *res;
	struct addrinfo *resorig;

	int sock = get_socket(client_addr, &res, &resorig, buffer);

	if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
		err(EXIT_FAILURE, "Connect error.");
	}

	char *buf = malloc(PACKET_MAX_LENGTH);
	if (buf == NULL) {
		send_error_msg(sock, NOT_DEFINED, "Internal server error, try again later.");
		errx(EXIT_FAILURE, "Could not allocate memory, request won't be satisified.");
	}

	set_ushort(buf, ACK);
	set_ushort(buf + 2, 0);

	char addr[INET_ADDRSTRLEN];
	if (inet_ntop(AF_INET, get_in_addr((struct sockaddr *)res->ai_addr), addr, sizeof (addr)) == NULL) {
		send_error_msg(sock, NOT_DEFINED, "Internal server error, try again later.");
		err(EXIT_FAILURE, "Inet_ntop error, request won't be satisfied.");
	}
	in_port_t port2 = ntohs(get_in_port((struct sockaddr *)res->ai_addr));

	if (send(sock, buf, 4, 0) == -1) { //ack 0 packet
		err(EXIT_FAILURE, "Send error.");
	}

	FILE *f = fopen(filename, "w");
	if (f == NULL) {
		send_error_msg(sock, NOT_DEFINED, "Could not create file for writing");
		errx(EXIT_FAILURE, "Could not create file for writing");
	}

	struct pollfd ufd;
	ufd.fd = sock;
	ufd.events = POLLIN;

	int retry_count = RETRY_COUNT;
	uint16_t last_acknowledged = 0;
	int cr_at_the_end = 0;

	int status = EXIT_FAILURE;

	int n;
	while (1) {
		errno = 0;
		int pol_res = poll(&ufd, 1, POLL_WAIT);
		if (pol_res == 0) {
			if (retry_count == 0) {
				send_error_msg(sock, NOT_DEFINED, "Send message timeout");
				fprintf(stderr, "Wasn't able to send message %i times, ending connection\n", RETRY_COUNT);
				break;
			}
			if (debug_flag) {
				fprintf(stderr, "Message lost, sending another one, tries: %i\n", retry_count);
			}
			if (send(sock, buf, 4, 0) == -1) { //ack 0 packet
				perror("Send error.");
				break;
			}
			retry_count--;
			continue;
		}

		if ((n = recv(sock, buf, PACKET_MAX_LENGTH, 0)) > 0) {
			if (debug_flag) {
				fprintf(stderr, "DBG: received %d bytes from %s:%d\n", n, addr, port2);
			}

			uint16_t opcode = get_ushort(buf);

			if (opcode == DATA) {
				uint16_t data_packet = get_ushort(buf + 2);

				if (data_packet < last_acknowledged + 1) { //we got old message
					continue;
				} else if (data_packet > last_acknowledged + 1) {
					fprintf(stderr, "Expected %i packet, not %i", last_acknowledged + 1, data_packet);
					break;
				}

				if (mode == OCTET) {
					fwrite(buf + 4, 1, n - 4, f);
				} else if (mode == NETASCII) {
					convert_from_net_and_write(buf, f, &cr_at_the_end, n);
				}

				set_ushort(buf, ACK);
				set_ushort(buf + 2, data_packet);

				if (send(sock, buf, 4, 0) == -1) { //ack packet
					perror("Send error.");
					break;
				}

				if (n < PACKET_MAX_LENGTH) {
					if (debug_flag) {
						fprintf(stderr, "Request completed.\n");
					}
					status = EXIT_SUCCESS;
					break;
				}

				last_acknowledged++;
			} else if (opcode == ERROR) {
				print_error_msg(buf);
				break;
			} else {
				fprintf(stderr, "Unexpected opcode %i\n", opcode);
				send_error_msg(sock, ILLEGAL_OP, "Unexpected opcode.");
				break;
			}
		}
	}

	fclose(f);
	free(buf);
	freeaddrinfo(resorig);
	close(sock);

	if (status == EXIT_FAILURE) {
		unlink(filename);
	}

	exit(status);
}

/**
 * Resolves read request.
 * @param port port from which client sent request
 * @param filename filename of the file to be read
 * @param client_addr address of the client
 * @param mode mode of the transfer
 */
void resolve_read_rq(int port, char *filename, char *client_addr, mode_type mode) {
	char buffer[6];
	snprintf(buffer, 6, "%d", port);

	struct addrinfo *res;
	struct addrinfo *resorig;

	int sock = get_socket(client_addr, &res, &resorig, buffer);
	if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
		perror("Connect error.");
		exit(EXIT_FAILURE);
	}

	char *buf = malloc(PACKET_MAX_LENGTH);
	if (buf == NULL) {
		send_error_msg(sock, NOT_DEFINED, "Internal server error, try again later.");
		errx(EXIT_FAILURE, "Could not allocate memory, request won't be satisified.");
	}

	char addr[INET_ADDRSTRLEN];
	if (inet_ntop(AF_INET, get_in_addr((struct sockaddr *)res->ai_addr), addr, sizeof (addr)) == NULL) {
		send_error_msg(sock, NOT_DEFINED, "Internal server error, try again later.");
		err(EXIT_FAILURE, "Inet_ntop error, request won't be satisfied.");
	}

	in_port_t port2 = ntohs(get_in_port((struct sockaddr *)res->ai_addr));

	struct pollfd ufd;
	ufd.fd = sock;
	ufd.events = POLLIN;

	FILE *f = fopen(filename, "r");
	if (f == NULL) {
		send_error_msg(sock, NOT_DEFINED, "Could not read file");
		errx(EXIT_FAILURE, "Could not read file");
	}

	int retry_count = RETRY_COUNT;

	uint16_t packets_sent = 1;
	int bytes_to_send = 0;

	int from_previous = 0;

	char *new_buf = malloc(2 * PACKET_MAX_LENGTH - 4);
	if (new_buf == NULL) {
		send_error_msg(sock, NOT_DEFINED, "Internal server error, try again later.");
		errx(EXIT_FAILURE, "Could not allocate memory, request won't be satisified.");
	}

	bytes_to_send = send_data(buf, new_buf, sock, &from_previous, f, mode, packets_sent);

	int status = EXIT_FAILURE;

	int n;
	while (1) {
		errno = 0;
		int pol_res = poll(&ufd, 1, POLL_WAIT);
		if (pol_res == 0) {
			if (retry_count == 0) {
				send_error_msg(sock, NOT_DEFINED, "Send message timeout");
				fprintf(stderr, "Wasn't able to send message %i times, ending connection\n", RETRY_COUNT);
				break;
			}
			if (debug_flag) {
				fprintf(stderr, "Message lost, sending another one, tries: %i\n", retry_count);
			}
			send_data(buf, new_buf, sock, &from_previous, f, mode, packets_sent);
			retry_count--;
			continue;
		}

		retry_count = RETRY_COUNT;

		if ((n = recv(sock, buf, PACKET_MAX_LENGTH, 0)) > 0) {
			if (debug_flag) {
				fprintf(stderr, "DBG: received %d bytes from %s:%d\n", n, addr, port2);
			}

			uint16_t opcode = get_ushort(buf);
			if (opcode == ACK) {
				uint16_t packet_num = get_ushort(buf + 2);
				if (packets_sent > packet_num) {
					continue;
				} else if (packets_sent < packet_num) {
					fprintf(stderr, "Incosistency between ACK and DATA packets, expected %i instead of %i\n", packets_sent, packet_num);
					break;
				}

				if (bytes_to_send < PACKET_MAX_LENGTH) { // we've got ack for the last one
					if (debug_flag) {
						fprintf(stderr, "Request completed.\n");
					}
					status = EXIT_SUCCESS;
					break;
				}

				packets_sent++;
				bytes_to_send = send_data(buf, new_buf, sock, &from_previous, f, mode, packets_sent);
			} else if (opcode == ERROR) {
				print_error_msg(buf);
				break;
			} else {
				fprintf(stderr, "Unexpected opcode %i\n", opcode);
				send_error_msg(sock, ILLEGAL_OP, "Unexpected opcode.");
				break;
			}
		} else if (n == -1) {
			perror("Recv error occured."); // TODO: maybe check for EAGAIN?
			break;
		}
	}

	fclose(f);
	free(new_buf);
	free(buf);
	freeaddrinfo(resorig);
	close(sock);
	exit(status);
}

/**
 * Handler for the SIGCHLD.
 * @param sig signam number
 */
void handle_sigchld(int sig) {
	int status;
	int saved_errno = errno;
	errno = 0;
	while (waitpid(-1, &status, WNOHANG) > 0) {
		if (errno) {
			perror("Waitpid error");
		}
	}
	errno = saved_errno;
}

/**
 * Registers handler for the SIGCHLD.
 */
void register_signal_handler() {
	struct sigaction sa;
	sa.sa_handler = &handle_sigchld;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; //SA_RESTART so certain system calls(e.g. recvfrom) won't have to be in loop when entering the signal handler, SA_NOCLDSTOP so we receive signal only when child is terminated
	if (sigaction(SIGCHLD, &sa, 0) == -1) {
		perror("Could not register signal handler");
		exit(EXIT_FAILURE);
	}
}

/**
 * Checks if the filename sent in request is correct.
 * @param file_name filename obtained from request
 * @param sock socket of the communication
 * @param faddr client address information
 * @param addrsize size of the address
 */
int check_filename(char *file_name, int sock, struct sockaddr_in *faddr, socklen_t addrsize) {
	int len = strlen(file_name);
	if (len == 0) {
		send_error_msg_to(sock, NOT_DEFINED, "Filename must have at least 1 char.", (struct sockaddr *)faddr, addrsize);
		return (1);
	} else if (len == 1 && file_name[0] == '.') { //TODO: should check this?
		send_error_msg_to(sock, ACCESS_VIOLATION, "Filename cannot be \".\".", (struct sockaddr *)faddr, addrsize);
		return (1);
	} else if (len == 2 && file_name[0] == '.' && file_name[1] == '.') { //TODO: should check this?
		send_error_msg_to(sock, ACCESS_VIOLATION, "Filename cannot be \"..\".", (struct sockaddr *)faddr, addrsize);
		return (1);
	}

	int i = 0;
	while (file_name[i]) {
		if (file_name[i] == '/') {
			send_error_msg_to(sock, ACCESS_VIOLATION, "Cannot contain slash in file name.", (struct sockaddr *)faddr, addrsize);
			return (1);
		}
		i++;
	}

	return (0);
}

void start_server(char *port, char *home, int test_flag) {
	register_signal_handler();
	int sock = get_socket_and_bind(NULL, port);

	char *buf = malloc(PACKET_MAX_LENGTH);
	if (buf == NULL) {
		errx(EXIT_FAILURE, "Could not allocate memory, request won't be satisified.");
	}

	char addr[INET_ADDRSTRLEN];

	struct sockaddr_in faddr;
	socklen_t addrsize = sizeof (faddr);

	int n;
	if (test_flag) {
		kill(getppid(), SIGUSR1);
	}
	while ((n = recvfrom(sock, buf, PACKET_MAX_LENGTH, 0, (struct sockaddr *)&faddr, &addrsize)) > 0) {
		if (inet_ntop(AF_INET, &faddr.sin_addr, addr, sizeof (addr)) == NULL) {
			perror("Inet_ntop error, request won't be satisfied.");
			continue;
		}
		int port = ntohs(faddr.sin_port);
		if (debug_flag) {
			fprintf(stderr, "DBG: received %d bytes from %s:%d\n", n, addr, port);
		}

		char file_name[FILENAME_MAX_LENGTH];
		char mode[MODE_MAX_LENGTH];

		int i = 0;
		int error_encountered = 0;
		while (buf[i + 2]) {
			if (i >= FILENAME_MAX_LENGTH) {
				fprintf(stderr, "Cannot process request, too long filename\n");
				error_encountered = 1;
				break;
			}
			file_name[i] = buf[i + 2];
			i++;
		}
		if (error_encountered) {
			continue;
		}
		file_name[i++] = '\0';
		int j = 0;
		while (buf[i + 2]) {
			mode[j] = buf[i + 2];
			if (j >= MODE_MAX_LENGTH) {
				fprintf(stderr, "Cannot process request, mode too long\n");
				error_encountered = 1;
				break;
			}
			j++;
			i++;
		}
		if (error_encountered) {
			continue;
		}
		mode[j] = '\0';

		string_to_upper(mode);

		mode_type m;
		if (!strncmp(mode, "NETASCII", 8)) {
			m = NETASCII;
		} else if (!strncmp(mode, "OCTET", 5)) {
			m = OCTET;
		} else {
			send_error_msg_to(sock, NOT_DEFINED, "Unknown mode.", (struct sockaddr *)&faddr, addrsize);
			fprintf(stderr, "Unknown mode: %s\n", mode);
			continue;
		}

		if (check_filename(file_name, sock, &faddr, addrsize))
			continue;

		int len = 0;
		if (home != NULL) {
			len = strlen(home);
		}
		char *new_file_name = malloc(len + FILENAME_MAX_LENGTH);
		if (new_file_name == NULL) {
			send_error_msg_to(sock, NOT_DEFINED, "Could not satisfy request because of the internal error, try again later.", (struct sockaddr *)&faddr, addrsize);
			fprintf(stderr, "Could not allocate needed memory, request won't be satisfied\n");
			continue;
		}
		i = 0;
		if (home != NULL) {
			while (home[i]) {
				new_file_name[i] = home[i];
				i++;
			}
			new_file_name[i++] = '/';
		}
		j = 0;
		while (file_name[j]) {
			new_file_name[i++] = file_name[j++];
		}
		new_file_name[i] = '\0';

		char *req = NULL;
		uint16_t opcode = get_ushort(buf);
		if (opcode == RRQ) {
			req = "RRQ";
			if (!does_file_exist_and_is_regular(new_file_name)) {
				send_error_msg_to(sock, FILE_NOT_FOUND, "File not found.", (struct sockaddr *)&faddr, addrsize);
				continue;
			}
		} else if (opcode == WRQ) {
			if (does_file_exist(new_file_name)) {
				send_error_msg_to(sock, FILE_EXISTS, "File already exists.", (struct sockaddr *)&faddr, addrsize);
				continue;
			}
			req = "WRQ";
		} else {
			send_error_msg_to(sock, ILLEGAL_OP, "Accepts only RRQ and WRQ requests.", (struct sockaddr *)&faddr, addrsize);
			continue;
		}

		if (debug_flag) {
			printf("%s request from %s:%d for %s in %s\n", req, addr, port, file_name, mode);
		}

		pid_t pid;
		switch (pid = fork()) {
			case -1:
				send_error_msg_to(sock, NOT_DEFINED, "Could not satisfy request because of the internal error, try again later.", (struct sockaddr *)&faddr, addrsize);
				fprintf(stderr, "Could not fork child process, request won't be satisfied\n");
				free(new_file_name);
				break;
			case 0: // I'm a child
				close(sock);
				free(buf);
				if (opcode == WRQ) {
					resolve_write_rq(port, new_file_name, addr, m);
				} else if (opcode == RRQ) {
					resolve_read_rq(port, new_file_name, addr, m);
				}
				break;
			default: // I'm a father
				free(new_file_name);
				break;
		}
	}

	if (n == -1) {
		perror("Recvfrom error occured."); // TODO: maybe check for EAGAIN?
	}

	close(sock);
	exit(EXIT_FAILURE);
}